package com.partsoft.dits.log;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;

import org.apache.log4j.Appender;
import org.apache.log4j.Layout;
import org.apache.log4j.Priority;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.OnlyOnceErrorHandler;
import org.apache.log4j.spi.ErrorHandler;
import org.apache.log4j.spi.Filter;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.OptionHandler;

/**
 * 后台线程日志添加器（LOG4J实现）
 */
public abstract class AbstractDaemonLogAppender implements Appender, OptionHandler {
	
	public static final String LOG_THREAD_NAME = "LOG_DAEMON";
	
	private ErrorHandler errorHandler;
	
	private String name;
	
	private Filter headFilter;
    
	private Filter tailFilter;
    
	private Layout layout;
	
	private boolean closed;
    
	private Priority threshold;
	
	private long appendThreadId;
	
	private Queue<LoggingEvent> logBuffer = new ConcurrentLinkedQueue<LoggingEvent>();
	
	private int logBufferSize = 150;
	
	private Semaphore logBufferSem = new Semaphore(logBufferSize);
	
	public AbstractDaemonLogAppender() {
		closed = false;
		errorHandler = new OnlyOnceErrorHandler();
		startDaemonThread();
	}
	
	//Implement OptionHandler
	public void activateOptions() {}
	
	//Implement Appender
	public void addFilter(Filter newFilter) {
		if (headFilter == null) {
			headFilter = tailFilter = newFilter;
		} else {
			tailFilter.setNext(newFilter);
			tailFilter = newFilter;
		}
	}
	
	public synchronized void setErrorHandler(ErrorHandler eh) {
		if (eh == null)
			LogLog.warn("You have tried to set a null error-handler.");
		else
			errorHandler = eh;
	}
	
	public boolean requiresLayout() {
		return true;
	}
	
	public void clearFilters() {
		headFilter = tailFilter = null;
	}
	
	public void setLogBufferSize(int i) {
		if (i == logBufferSize) return;
		int de = i - logBufferSize;
		if (i > 0) logBufferSem.release(de);
		else try {
			logBufferSem.acquire(i);
		} catch (InterruptedException e) {}
	}

	public ErrorHandler getErrorHandler() {
		return errorHandler;
	}

	public Filter getFilter() {
		return headFilter;
	}

	public final Filter getFirstFilter() {
		return headFilter;
	}
	
    public Layout getLayout() {
        return layout;
    }
    
	public void setLayout(Layout layout) {
		this.layout = layout;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public Priority getThreshold() {
		return threshold;
	}

	public void setThreshold(Priority threshold) {
		this.threshold = threshold;
	}
	
	protected boolean isAppendThread() {
		return Thread.currentThread().getId() == appendThreadId;
	}

	public synchronized void doAppend(LoggingEvent event) {
		if (isClosed()) {
			LogLog.error("Attempted to append to closed appender named [" + getName() + "].");
			return;
		}
		if(!isAsSevereAsThreshold(event.getLevel()) || isAppendThread()) return;
		event.getThreadName();
		Filter f = this.headFilter;

	    while (f != null) {
	    	int d = f.decide(event);
	    	if (Filter.DENY == d) return;
	    	if (Filter.ACCEPT == d) break;
	    	f = f.getNext();
		}
		try {
			logBufferSem.acquire();
		} catch (InterruptedException e) {
		}
		logBuffer.offer(event);
	}
	
	
	public void close() {
		this.closed = true;
	}
	
	//Self implements
	public boolean isAsSevereAsThreshold(Priority priority) {
		return threshold == null || priority.isGreaterOrEqual(threshold);
	}
	
	public boolean isClosed() {
		return closed;
	}

	private void startDaemonThread() {
		Thread appendThread = new Thread(new ThreadLogWritter(), LOG_THREAD_NAME);
		appendThread.setDaemon(true);
		appendThread.start();
		appendThreadId = appendThread.getId();
		LogLog.debug("Append daemon thread id is " + Long.toString(appendThreadId) + ".");
	}

	private void doLogSave() {
		int sleepTime = 1000;
		while(true) {
			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {}
			if (Thread.currentThread().isInterrupted() || isClosed()) break;
			LoggingEvent event = null;
			while((event = logBuffer.poll()) != null ) {
				logBufferSem.release();
				try {
					append(event);
				} catch (Throwable e) {
					LogLog.error(String.format("写入日志失败:%s", e.getMessage()), e);
				}
				if (Thread.currentThread().isInterrupted() || isClosed()) break;
			}
			if (Thread.currentThread().isInterrupted() || isClosed()) break;
		}
	}
	
	protected abstract void append(LoggingEvent event);
	
    public void finalize() {
        if(isClosed()) {
            return;
        } else {
            LogLog.debug("Finalizing appender named [" + getName() + "].");
            close();
            return;
        }
    }
	
	private class ThreadLogWritter implements Runnable {
		public void run() {
			doLogSave();
		}
	}

}
